package org.semanticflash.rdf.io.ntriple
{


	import com.bigflexlib.x.RegExp.RegExpUtil;
	import com.bigflexlib.x.String.StringUtil;
	
	import org.semanticflash.rdf.triple.RDFTriple;
	import org.semanticflash.rdf.values.IRI;
	import org.semanticflash.rdf.values.RDFLangLiteral;
	import org.semanticflash.rdf.values.RDFLiteral;
	import org.semanticflash.rdf.values.RDFPlainLiteral;
	import org.semanticflash.rdf.values.RDFTypedLiteral;
	import org.semanticflash.rdf.values.RDFValue;
	

	/**
	 * 
	 * http://www.w3.org/TR/rdf-testcases/#ntriples
	 * 
	 * A manual procedural parser for the NTriple syntax.
	 * This "works" but should be replaced by a more performant, error friendly
	 * ANTLR/FlashLR generated parser later on.
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class NTripleParser2
	{
		
		
		
		
		
		/**
		 * 
		 * TODO: parse the doc line by line instead of splitting it
		 * 
		 * @param ntDoc
		 * @return 
		 * 
		 */		
		internal static function docToLines( ntDoc:String ):Array
		{
		
			var lines:Array;
			
			// replace all returns by newlines
			ntDoc = StringUtil.replace( ntDoc, "\r", "\n" );				
			
			// collapse newlines and split
			lines = ntDoc.replace( /\n+/gi, "\n" ).split( "\n" );
			
			return lines;
		
		}
		
		
		public static function map_nt_RDFValue( item:*, index:int, array:Array):RDFValue
		{
			return parseValue( item );
		}
		
		public static function map_nt_RDFValue_arr( arr:Array ):Array
		{
			return arr.map( map_nt_RDFValue );
		}		


		// TODO: consider TABs as WS	
		public static function parseTriple( str:String, bNodeContext:IRI = null ):RDFTriple
		{
		
			var parts:Array = str.split( " " );	
			
			// we need the first two non-ws parts
			// these are the subject and predicate
			
			var part		:String;
			var nonWSParts	:Array = [ ];
			
			while ( nonWSParts.length != 2 )
			{
				part = parts.shift( );	
				if( part.length > 0 )
					nonWSParts.push( part );
			}
			
			
			
			// these two are ready
			var s:IRI = parseValue( nonWSParts[ 0 ] ) as IRI;
			
			if ( s == null )
				return null; // TODO error
			
			
			var p:IRI = parseValue( nonWSParts[ 1 ] ) as IRI;
			
			if ( p == null || p.isBNode )
				return null; // TODO error			
			
			
			
			// but this one might have surrounding WS and an optional trailing dot
			var oString:String = parts.join( " " );
			
			oString = RegExpUtil.trim( oString );
			
			if ( oString.charAt( oString.length-1 ) == "." )
				oString = oString.substr( 0, oString.length-1 );
			
			
			var o:RDFValue = parseValue( oString );
			
			if ( o == null )
				return null; // TODO error				
			
			
			return RDFTriple.instance( s, p, o );
		
		}		








		/**
		 * 
		 * 
		 * @param str
		 * @param bNodeContext
		 * @return 
		 * 
		 */		
		public static function parseValue( str:String, bNodeContext:IRI = null ):RDFValue
		{
			
			switch ( str.charAt( 0 ) )
			{
				case "<":
					return parseIRI( str );
				
				case "_":
					return parseBNode( str, bNodeContext );
			
				case '"':
					return parseLiteral( str );
			}
					
			return null; // TODO: friendly error msg						
		
		}
		
		
		
		public static function parseIRI( str:String ):IRI
		{
			str = StringUtil.trimEdges( str, 1 );
			str = StringUtil.decodeUnicode( str );
			return IRI.instance(  str ); 				
		}
		
		
		
		
		public static function parseBNode( str:String, bNodeContext:IRI = null ):IRI
		{
			return IRI.bNode( bNodeContext, str );		
		}
		
		
		
		
		public static function parseLiteral( str:String ):RDFLiteral
		{
			
			
			var quotes:Array = StringUtil.findFirstQuotePair( str );
			
			if ( quotes == null )
				return null;			
			
			var lexicalValue:String = str.substring( quotes[0]+1, quotes[1] );


			var tkn:String = StringUtil.getNonOccurringToken( lexicalValue, "^´&$%!" );	// a sequence that is not contained in the string
			
			
			lexicalValue = StringUtil.replace( lexicalValue, "\\\\", tkn );					// escaped backslashes are masked with the token			

			lexicalValue = StringUtil.replaceMany( lexicalValue, [ 
											["\\n","\n"], 
											["\\r","\r"],
											['\\"','"']
										] );
			
			lexicalValue = StringUtil.decodeUnicode( lexicalValue );
			
			lexicalValue = StringUtil.replace( lexicalValue, tkn, "\\" );					// and recovered later on
			
			
			
			// look for language or datatype
			
			str = str.substr( quotes[1] + 1 );	// get remaining string
			str = str.split(" ").join( "" );		// collapse WS
			
			
			if ( str.length == 0 )
				return RDFPlainLiteral.instance( lexicalValue );
			
			
			if ( str.charAt( 0 ) == "@" )
			{
			
				var lang:String = str.substr( 1 );
				
				return RDFLangLiteral.instance( lexicalValue, lang );
			
										
			}
			else if ( str.charAt( 0 ) == "^" )
			{
			
				str = str.substr( 2 );
				var dt:IRI = parseIRI( str );			

				return RDFTypedLiteral.instance( lexicalValue, dt );			
			
			}
			
			
			return null;
				
		}
		
		

		

	}

}
